資料庫遷移是以執行一個個檔案來逐步建立資料庫表單的作法,可以紀錄資料庫變化的過程。逐步變更可以降低對已上線系統的影響,也能在出錯的時候退回到還能正常運作時的資料庫結構。
可以先來看看 Laravel 預先建立好的 Migration 檔案
// database\migrations\2014_10_12_000000_create_users_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
這是用來建立 User 表單的遷移,首先可以看到主要分成了 up / down 兩個功能, up 就是推進遷移時執行, down 則是退回遷移步驟時執行。
up 不一定是建立或增加欄位,可能會是刪除欄位,變更欄位屬性(移除 Foreign key 等),或是刪除表單也有可能,而 down 中需要對應 up 的內容執行相反的指令。
如果還沒跑過可以先跑一次 Migration 建立使用者登入相關的表單
sail artisan migrate
成功的話可以在資料庫看到新建的表單
用指令建立 Migration 檔案
sail artisan make:migration <migratoin_name>
這樣建出來的 Migration 會放在 database/migrations 目錄下,並且 up / down 方法都是空的。
如果 Migration 是用來建立表單的話,可以在指令中指定要建立的表單名稱
sail artisan make:migration create_todos_table --create todos
這樣建立的 Migration 中就會預設好建立表單的指令, down 也會填好刪除表單的指令。
// database\migrations\XXXX_XX_XX_XXXXX_create_todos_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTodosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('todo');
}
}
另外說一下 Migration 檔案都會在檔名前面加上時間戳,以辨別 Migration 的執行順序。
如果不是想建立表單而是變更某個表單的內容的話,指令會變成
sail artisan make:migration update_todos_table --table todos
另外如果想指定 migrations 建立的位置,指令可以加上 path 參數
sail artisan make:migration update_todos_table --table todos --path database/migrations/Todos
path 會是相對於專案目錄的位置
如果是在 Migration 中增改刪除表單,都是利用 Schema 這個幫手。
Schema::create() 方法會建立新的表單
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
up() 裡是建立表單的話,down() 裡就會寫刪除表單的方法
public function down()
{
Schema::dropIfExists('todos');
}
關於表單的名稱,一般會加上 's' ,這樣之後建立 Eloquent ORM 的模型時,就不用在特地宣告模型對應的表單,Eloquent ORM 會自動對應,例如 Flight 模型會自動找 flights 表單進行查詢。
當然如果名稱對不上的話,手動宣告模型對應的表單就好。
建立表單的同時,可以宣告表單要有的欄位。
欄位的定義包含名稱、型別、是否可為空等,這邊列一些比較常用的。
$table->uuid('id'); // id -> 第一個參數是欄位名稱,其他方法也一樣
建立值為 UUID 的欄位,通常用來當主鍵,或宣告外鍵欄位。
$table->boolean('confirmed');
$table->integer('votes');
$table->float('amount', 3, 2); // 3->數字總數 , 2->小數位數 ,例: 1.23
$table->string('name', 100); //100->字數上限
$table->longText('description');
$table->dateTime('created_at');
常用型別以及日期時間型別
$table->json('options');
可以把大量資料包成 json 存起來,要用的時候再解析,我們這通常用來保存機器的通訊數據原始資料。
$table->timestamps();
一次建立常用的 created_at 跟 updated_at 欄位。
$table->string('description')->nullable();
nullable 宣告該欄位可為空值,沒有宣告 nullable 的話當欄位沒值會報錯。
$table->bool('receive_ads')->default(true);
當未宣告欄位值的話自動填入預設的值,就不用宣告 nullable 了。
$table->string('email')->unique();
宣告必須為獨特值的欄位,該欄位內的所有值必須是獨一無二的,寫入時若發現有重複會報錯。
建立表單時也可以加上表單間的關聯性,通常是宣告一個外鍵後綁定到其他表單的主鍵上。
$table->uuid('user_id');
$table->foreign('user_id')->references('id')->on('users');
要注意的是當宣告關連到 users 時, users 表單必須已經建立,而且有 id 這個欄位。
這是資料庫層的限制,之後當寫入值的時候如果 user_id 為空或找不到任何 users 表單中的參照,會報錯。
可以進一步宣告當資料被刪除的話要如何處裡關聯的資料。
像是建立一個 user_settings 表單,當中的資料都會關連到 user
public function up()
{
Schema::create('user_settings', function (Blueprint $table) {
$table->uuid('id');
$table->uuid('user_id');
$table->foreign('user_id')->references('id')->on('users')
->onDelete('cascade');
$table->timestamps();
});
}
宣告 onDelete('cascade') 的話,當 user 被刪除時,跟他關聯的 user_setting 資料也會被刪除。
其他選項
onDelete('restrict'); //阻止刪除動作,除非 user_setting 被刪除不然 user 刪不了
onDelete('set null'); //刪除後 user_setting 的 user_id 為 null
//要將 user_id 設為 nullable 不然會報錯
也有 onUpdate,當關聯資料被更新時執行,選項跟 onDelete 一樣。
可以更改既有的表單名稱
Schema::rename($from, $to);
或是刪除表單
Schema::drop('users'); //找不到表單的話會報錯
Schema::dropIfExists('users');
要變更現存的表單內的欄位的話,用 Schema::table()
public function up()
{
Schema::table('user_settings', function (Blueprint $table) {
//
});
}
如果是新增欄位,做法跟建立表單時相同,直接宣告就好
Schema::table('user_settings', function (Blueprint $table) {
$table->integer('height');
});
如果要變更現有的欄位,首先要安裝套件
sail composer require doctrine/dbal
然後用套件的 change 方法宣告是要變更現有欄位。
Schema::table('user_settings', function (Blueprint $table) {
$table->string('name', 50)->nullable()->change();
});
套件也有提供變更欄位名稱的方法
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('from', 'to');
});
然後是刪除欄位的方法,這個也是套件的功能
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('votes');
$table->dropColumn(['avatar', 'location']); //刪除多個
});
想將之前設定的外鍵關聯移除的話
$table->dropForeign(['user_id']);
這樣只會將關聯性移除,欄位會保留
如果是要變更現有的關聯關係的話,只能先移除關聯後建立新的關聯
$table->dropForeign(['user_id']);
$table->foreign('user_id')->references('id')->on('users')
->onDelete('set_null');
刪除的話也是一樣
$table->dropForeign(['user_id']);
$table->dropColumn('user_id');
一般會用 id 欄位作為主鍵值
$table->uuid('id')->primary();
若查詢時沒有指定欄位,就會以主鍵欄位做查詢。
也可以在宣告完所有欄位後再指定 primary 的欄位 ,閱讀上會比較容易辨識到主鍵的設定
$table->uuid('id');
...
$table->primary('id');
注意 primary 宣告必須在欄位被建立後才能執行,不然會報錯。
除了主鍵外有時也需要必須唯獨特值的欄位
$table->string('email')->unique();
跟 primary 一樣,也可以事後宣告
$table->string('email');
$table->unique('email');
另外偶爾會有需要聯合多個欄位為鍵值的設計
$table->primary(['account_id', 'created_at'],);
$table->unique(['account_id', 'created_at']);
要移除鍵值的設定的話,以預設的鍵值名稱作為參照
$table->dropPrimary('users_id_primary'); //移除 users 表單中 id 的 primary 設定
$table->dropUnique('users_email_unique'); //移除 users 表單中 email 的 unique 鍵
Laravel 預設的鍵值名稱會以 "表單_欄位_屬性" 構成,如果不想要預設的名稱的話,可以在宣告鍵值時設定名稱
$table->primary(['account_id', 'created_at'],"account_created_at");
之後移除時以自訂的名稱作為參照
$table->dropPrimary('account_created_at');
執行 migration 的基礎指令是
sail artisan migrate
這個指令會從最後一次 migration 的檔案之後開始執行 migration。
如果想退回 migration
sail artisan migrate:rollback //退回一個 migration
sail artisan migrate:rollback --step=5 //退回 5 個
sail artisan migrate:reset //退回全部
如果想要從頭開始執行 migration
sail artisan migrate:refresh // 一步步退回全部後再執行全部
sail artisan migrate:fresh // 不執行退回而是直接刪除所有表單,再執行全部
如果在執行完 migration 後想要植入種子資料
sail artisan migrate --seed
開發上最常用的就是整個資料庫清空後重新建表單跟塞種子資料
sail artisan migrate:fresh --seed